package com.smartleanx.module.hrm.controller.admin.payroll;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.smartleanx.framework.common.enums.BizTypeEnum;
import com.smartleanx.framework.common.pojo.CommonResult;
import com.smartleanx.framework.common.pojo.PageParam;
import com.smartleanx.framework.common.pojo.PageResult;
import com.smartleanx.framework.common.util.collection.CollectionUtils;
import com.smartleanx.framework.common.util.object.BeanUtils;
import com.smartleanx.framework.customfield.core.service.CustomFieldDataService;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataListReqBO;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataRespBO;
import com.smartleanx.framework.datapermission.core.util.DataPermissionUtils;
import com.smartleanx.framework.excel.core.util.ExcelUtils;
import com.smartleanx.framework.operatelog.core.annotations.OperateLog;
import com.smartleanx.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.smartleanx.module.hrm.controller.admin.employee.vo.EmployeeQueryRepVo;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailCommonReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailListReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailPageReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailSaveReqVO;
import com.smartleanx.module.hrm.dal.dataobject.employee.EmployeeDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDetailDO;
import com.smartleanx.module.hrm.eums.employee.EmployeeStatusEnum;
import com.smartleanx.module.hrm.eums.payroll.PayrollSendStatusEnum;
import com.smartleanx.module.hrm.eums.payroll.PayrollStateEnum;
import com.smartleanx.module.hrm.intergration.CustomFieldServiceProvider;
import com.smartleanx.module.hrm.service.employee.EmployeeService;
import com.smartleanx.module.hrm.service.payroll.PayrollDetailService;
import com.smartleanx.module.hrm.service.payroll.PayrollService;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldReqDTO;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldRespDTO;
import com.smartleanx.module.system.enums.customfield.CustomFieldLabelEnum;
import com.smartleanx.module.system.enums.customfield.CustomFieldTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

import static com.smartleanx.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.smartleanx.framework.common.pojo.CommonResult.success;
import static com.smartleanx.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static com.smartleanx.module.hrm.enums.ErrorCodeConstants.PAYROLL_NOT_EXISTS;

@Tag(name = "管理后台 - 薪资详情")
@RestController
@RequestMapping("/hrm/payroll-detail")
@Validated
public class PayrollDetailController {

    @Resource
    private PayrollDetailService payrollDetailService;

    @Resource
    private CustomFieldDataService customFieldDataService;

    @Resource
    private EmployeeService employeeService;

    @Resource
    private CustomFieldServiceProvider customFieldServiceProvider;

    @Resource
    private PayrollService payrollService;

    @PutMapping("/update")
    @Operation(summary = "更新薪资详情(确认导入、在线编辑)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:update')")
    public CommonResult<Boolean> updateDetail(@Valid @RequestBody PayrollDetailSaveReqVO updateReqVO) {
        payrollDetailService.updateDetail(updateReqVO);
        return success(true);
    }

    @DeleteMapping("/delete")
    @Operation(summary = "删除薪资详情")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:delete')")
    public CommonResult<Boolean> deleteDetail(@RequestParam("id") Long id) {
        payrollDetailService.deleteDetail(id);
        return success(true);
    }

    @PutMapping("/approve")
    @Operation(summary = "提交审核")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:approve')")
    public CommonResult<Boolean> approve(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        if(CollUtil.isEmpty(reqVO.getIds())){
            PayrollDetailPageReqVO pageReqVO = PayrollDetailPageReqVO.builder().payrollId(reqVO.getPayrollId())
                    .webPage(CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue()).build();
            pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
            reqVO.setIds(CollectionUtils.convertList(payrollDetailService.getDetailPage(pageReqVO).getList(), PayrollDetailDO::getId,
                    detail -> !(BpmProcessInstanceStatusEnum.RUNNING.getStatus().equals(detail.getApproveResult()) ||
                            (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(detail.getApproveResult()) &&
                                    !PayrollSendStatusEnum.WITHDRAW.getValue().equals(detail.getSendStatus())))));
        }
        payrollDetailService.approve(reqVO);
        return success(true);
    }

    @PutMapping("/generate-pay-stub")
    @Operation(summary = "重新生成工资条")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:generate-pay-stub')")
    public CommonResult<Boolean> generatePayStub(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        if(CollUtil.isEmpty(reqVO.getIds())){
            PayrollDetailPageReqVO pageReqVO = PayrollDetailPageReqVO.builder().payrollId(reqVO.getPayrollId())
                    .webPage(CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue()).build();
            pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
            reqVO.setIds(CollectionUtils.convertList(payrollDetailService.getDetailPage(pageReqVO).getList(), PayrollDetailDO::getId));
        }
        payrollDetailService.generatePayStub(reqVO);
        return success(true);
    }

    @PutMapping("/send-pay-stub")
    @Operation(summary = "发送工资条(全部发送、发送)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:send-pay-stub')")
    public CommonResult<Boolean> sendPayStub(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        if(CollUtil.isEmpty(reqVO.getIds())){
            PayrollDetailPageReqVO pageReqVO = PayrollDetailPageReqVO.builder().payrollId(reqVO.getPayrollId()).archived(false)
                    .webPage(CustomFieldLabelEnum.PAYROLL_SLIP_INFO.getValue()).build();
            pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
            reqVO.setIds(CollectionUtils.convertList(payrollDetailService.getDetailPage(pageReqVO).getList(), PayrollDetailDO::getId));
        }
        payrollDetailService.sendPayStub(reqVO);
        return success(true);
    }

    @PutMapping("/withdraw-pay-stub")
    @Operation(summary = "撤回工资条")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:withdraw-pay-stub')")
    public CommonResult<Boolean> withdrawPayStub(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        payrollDetailService.withdrawPayStub(reqVO);
        return success(true);
    }

    @PutMapping("/confirm-pay-stub")
    @Operation(summary = "工资确认")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:confirm-pay-stub')")
    public CommonResult<Boolean> confirmPayStub(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        payrollDetailService.confirmPayStub(reqVO);
        return success(true);
    }

    @PutMapping("/cancel-pay-stub")
    @Operation(summary = "工资取消确认")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:cancel-pay-stub')")
    public CommonResult<Boolean> cancelPayStub(@Valid @RequestBody PayrollDetailCommonReqVO reqVO) {
        payrollDetailService.cancelPayStub(reqVO);
        return success(true);
    }

    @GetMapping("/list")
    @Operation(summary = "获得薪资详情列表(在线编辑)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:query')")
    public CommonResult<List<Map<String, CustomFieldDataRespBO>>> getPayrollDetailList(@Valid PayrollDetailListReqVO reqVO) {
        PayrollDetailPageReqVO pageReqVO = BeanUtils.toBean(reqVO, PayrollDetailPageReqVO.class);
        pageReqVO.setWebPage(CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue());
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<PayrollDetailDO> detailList = CollectionUtils.filterList(payrollDetailService.getDetailPage(pageReqVO).getList(),
                detail -> !(BpmProcessInstanceStatusEnum.RUNNING.getStatus().equals(detail.getApproveResult()) ||
                        (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(detail.getApproveResult()) &&
                                !PayrollSendStatusEnum.WITHDRAW.getValue().equals(detail.getSendStatus()))));
        if(CollUtil.isEmpty(detailList)){
            return success(Collections.emptyList());
        }
        PayrollDO payroll = payrollService.getPayroll(reqVO.getPayrollId());
        CustomFieldDataListReqBO reqBO = CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue()).labels(reqVO.getLabels())
                .recordIds(CollectionUtils.convertList(detailList, PayrollDetailDO::getId))
                .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())? payroll.getId(): 0).build();
        List<CustomFieldDataRespBO> fieldDataList = customFieldDataService.getDataList(reqBO);
        return success(customFieldDataService.parseData(reqBO, getFieldDataListWithEmployeeInfo(detailList, fieldDataList)));
    }

    @GetMapping("/page")
    @Operation(summary = "获得薪资详情分页(薪资管理列表、工资条总览、发放记录)")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:query')")
    public CommonResult<PageResult<Map<String, CustomFieldDataRespBO>>> getPayrollDetailPage(@Valid PayrollDetailPageReqVO pageReqVO) {
        if(StrUtil.isNotBlank(pageReqVO.getKeyword()) || StrUtil.isNotBlank(pageReqVO.getEmployeeName()) ||
                Objects.nonNull(pageReqVO.getDeptId()) || Objects.nonNull(pageReqVO.getEmployeeState())){
            List<Long> employeeIds = employeeService.getEmployeeIdsByQuery(BeanUtil.toBean(pageReqVO, EmployeeQueryRepVo.class));
            if(CollUtil.isEmpty(employeeIds)){
                return success(PageResult.empty());
            }
            pageReqVO.setEmployeeIds(employeeIds);
        }
        PageResult<PayrollDetailDO> pageResult = payrollDetailService.getDetailPage(pageReqVO);
        List<PayrollDetailDO> detailList = pageResult.getList();
        if(CollUtil.isEmpty(detailList)){
            return success(new PageResult<>(Collections.emptyList(), pageResult.getTotal()));
        }
        PayrollDO payroll = payrollService.getPayroll(pageReqVO.getPayrollId());
        CustomFieldDataListReqBO reqBO = CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue()).labels(pageReqVO.getLabels())
                .recordIds(CollectionUtils.convertList(detailList, PayrollDetailDO::getId))
                .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())? payroll.getId(): 0).build();
        List<CustomFieldDataRespBO> fieldDataList = customFieldDataService.getDataList(reqBO);
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<CustomFieldRespDTO> fieldRespDTOs = CollectionUtils.filterList(customFieldServiceProvider.getFieldList(
                CustomFieldReqDTO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .labels(Collections.singletonList(CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue()))
                        .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState()) ? payroll.getId() : 0).build()),
                field -> CustomFieldTypeEnum.NUMBER.getValue().equals(field.getType()));
        List<PayrollDetailDO> allDetailList = payrollDetailService.getDetailPage(pageReqVO).getList();
        Map<Long, List<CustomFieldDataRespBO>> map = CollectionUtils.convertMultiMap(customFieldDataService.getDataList(
                CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue()).labels(pageReqVO.getLabels())
                        .recordIds(CollectionUtils.convertList(allDetailList, PayrollDetailDO::getId))
                        .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())? payroll.getId(): 0).build()), CustomFieldDataRespBO::getFieldId);
        List<Map<String, CustomFieldDataRespBO>> respVOList = customFieldDataService.parseData(reqBO, getFieldDataListWithEmployeeInfo(detailList, fieldDataList));
        respVOList.add(CollectionUtils.convertMap(fieldRespDTOs, CustomFieldRespDTO::getColName, field -> {
            CustomFieldDataRespBO respBO = BeanUtil.toBean(field, CustomFieldDataRespBO.class);
            if(!"effective_salary".equals(field.getColName())){
                respBO.setFieldValue(
                    Objects.toString(
                        CollectionUtils.getSumValue(
                            CollectionUtils.filterList(map.get(field.getId()), fieldData -> StrUtil.isNotBlank(fieldData.getFieldValue())),
                            fieldData -> BigDecimal.valueOf(Double.parseDouble(fieldData.getFieldValue().replaceAll("[^0-9.-]",""))),
                            BigDecimal::add,
                            BigDecimal.ZERO
                        )
                    )
                );
            }else{
                respBO.setFieldValue(Objects.toString(CollectionUtils.getSumValue(allDetailList,
                        detail -> BigDecimal.valueOf(Double.parseDouble(detail.getEffectiveSalary().replaceAll("[^0-9.-]",""))), BigDecimal::add, BigDecimal.ZERO)));
            }
            return respBO;
        }));
        return success(new PageResult<>(respVOList, pageResult.getTotal()));
    }

    @GetMapping("/export")
    @Operation(summary = "导出薪资详情")
    @PreAuthorize("@ss.hasPermission('hrm:payroll-detail:export')")
    @OperateLog(type = EXPORT)
    public void exportDetailList(@Valid Long payrollId, HttpServletResponse response) throws IOException {
        PayrollDO payroll = payrollService.getPayroll(payrollId);
        if(Objects.isNull(payroll)){
            throw exception(PAYROLL_NOT_EXISTS) ;
        }
        List<CustomFieldRespDTO> fieldList = customFieldServiceProvider.getFieldList(
                CustomFieldReqDTO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .labels(CollUtil.newArrayList(CustomFieldLabelEnum.PAYROLL_EMPLOYEE_INFO.getValue(),
                                CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue()))
                        .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState()) ? payroll.getId() : 0).build());
        List<PayrollDetailDO> detailList = CollectionUtils.filterList(payrollDetailService.getDetailListByPayrollId(payrollId), PayrollDetailDO::getArchived);
        CustomFieldDataListReqBO reqBO = CustomFieldDataListReqBO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                .labels(CollUtil.newArrayList(CustomFieldLabelEnum.PAYROLL_EMPLOYEE_INFO.getValue(),
                        CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue()))
                .recordIds(CollectionUtils.convertList(detailList, PayrollDetailDO::getId))
                .version(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())? payroll.getId(): 0).build();
        List<CustomFieldDataRespBO> fieldDataList = customFieldDataService.getDataList(reqBO);
        List<CustomFieldDataRespBO> respBOList = getFieldDataListWithEmployeeInfo(detailList, fieldDataList);

        List<List<String>> head = new ArrayList<>();
        List<String> colList = new ArrayList<>();
        // 构建
        for (CustomFieldRespDTO field : fieldList) {
            head.add(Collections.singletonList(field.getName()));
            colList.add(field.getColName());
        }
        List<List<String>> data = CollectionUtils.convertList(customFieldDataService.parseData(reqBO, respBOList),
                map -> CollectionUtils.convertList(colList, col -> Objects.toString(map.get(col).getFieldValue(), "")));
        ExcelUtils.write(response, "薪资导出表.xlsx", "薪资导出表", head, data, null);
    }

    private List<CustomFieldDataRespBO> getFieldDataListWithEmployeeInfo(List<PayrollDetailDO> detailList, List<CustomFieldDataRespBO> fieldDataList) {
        Map<Long, List<CustomFieldDataRespBO>> fieldDataMap = CollectionUtils.convertMultiMap(fieldDataList, CustomFieldDataRespBO::getRecordId);
        Map<Long, EmployeeDO> employeeMap = new HashMap<>();
        DataPermissionUtils.executeIgnore(() -> employeeService.getEmployeeList(
                CollectionUtils.convertList(detailList, PayrollDetailDO::getEmployeeId)).forEach(employee -> employeeMap.put(employee.getId(), employee)));
        List<CustomFieldDataRespBO> respBOList = new ArrayList<>();
        detailList.forEach(detail -> {
            Map<String, Object> detailMap = BeanUtil.beanToMap(detail);
            if(employeeMap.containsKey(detail.getEmployeeId())){
                EmployeeDO employee = employeeMap.get(detail.getEmployeeId());
                detailMap.put("employeeNo", employee.getEmployeeNo());
                detailMap.put("employeeDeptId", employee.getDeptId());
                detailMap.put("employeePostId", employee.getPostId());
                detailMap.put("employeePostLevelId", Objects.toString(employee.getPostLevelId(), ""));
                detailMap.put("employeeState", EmployeeStatusEnum.getName(employee.getState()));
            }
            respBOList.addAll(CollectionUtils.convertList(fieldDataMap.get(detail.getId()), fieldData -> {
                String key = StringUtils.underlineToCamel(fieldData.getColName());
                if(detailMap.containsKey(key)){
                    fieldData.setFieldValue(Objects.toString(detailMap.get(key)));
                }
                return fieldData;
            }));
        });
        return respBOList;
    }

}